// ProcessTable.h
//
// This class represents a singleton data structure to manage and update all data related to currently running
// processes and threads in nachos. This data will be updated on Fork and Exec when new processes and threads
// are launched. It will be used and updated in Exit to determine how to fully close down processes, threads,
// or possibly the whole system.
//
// Inaddition to how many processes and threads are currently running the table will manage parent-child
// hierarchies between them to make sure parents are not exited until all of their children are.

#include "ProcessTable.h"
#include "system.h"
#include "synch.h"

KernelProcessEntry::KernelProcessEntry() {
	numChildProcesses 	= 0;
	parentSpace 		= NULL;
	currentSpace 		= NULL;
	//childProcessTableMap = new BitMap(MaxChildSpaces);
	//childProcesses 	= new ChildProcEntry[MaxChildSpaces];
	threadTable 		= new Table(MAXNUMTHREADS);
	numThreads 			= 0;
	dataLock 			= new Lock("Process Entry Data Lock");
	ExitCV 				= new Condition("Process Entry Exit CV");
}

KernelProcessEntry::~KernelProcessEntry() {
	delete threadTable;
	delete dataLock;
	delete ExitCV;
}

KernelThreadEntry::KernelThreadEntry() {
	numChildThreads = 0;
    parentThread 	= NULL;
    ExitLock 		= new Lock("Thread Entry Exit Lock");
    ExitCV 			= new Condition("Thread Entry Exit Condition");
    //isExiting 		= false;
}

KernelThreadEntry::~KernelThreadEntry() {
	delete ExitLock;
	delete ExitCV;
}

ProcessTable* ProcessTable::instance = NULL;

ProcessTable* ProcessTable::getInstance() {
	if (instance == NULL) {
		instance = new ProcessTable();
	}
	return instance;
}

void ProcessTable::deleteProcessTable() {
	delete instance;
}

ProcessTable::ProcessTable() {
	processTable = new Table(MAXNUMPROCESSES);
	numProcessesLock = new Lock("Process Table numProc Lock");
	numProcesses = 0;
}

ProcessTable::~ProcessTable() {
	delete numProcessesLock;
	delete processTable;
}

SpaceId ProcessTable::addNewProcess(AddrSpace* space){
	KernelProcessEntry* processEntry = new KernelProcessEntry();
	int i = processTable->Put(processEntry);
	if(i != -1){
		 incrementNumProcesses(); // increment the count of processes
		 processEntry->currentSpace = space;
	} else {
		   DEBUG('a', "Process Table Full. Unable to add new ProcessTable Process Entry.\n");
		   interrupt->Halt();
	}
	return i;
}
ThreadId ProcessTable::addNewThread(SpaceId processId){
	DEBUG('b', "addNewThread: adding a new thread to process %d.\n", processId);
	if (processId < 0 || processId > MAXNUMPROCESSES || processTable->Get(processId) == 0) {
		DEBUG('a', "Invalid SpaceId %d given when trying to add a new thread to ProcessTable\n", processId);
		interrupt->Halt();
	}
	
	KernelProcessEntry* processEntry = ((KernelProcessEntry*)(processTable->Get(processId)));
	KernelThreadEntry*  threadEntry  = new KernelThreadEntry();
	int i = processEntry->threadTable->Put(threadEntry);
	if (i != -1){
		(processEntry->numThreads)++; // increment the count of running threads in this process
		DEBUG('b', "addNewThread: increased numThreads to %d for process %d.\n", processEntry->numThreads, processId);
		
		// temp test
		KernelProcessEntry* testProcessEntry = ((KernelProcessEntry*)(processTable->Get(processId)));
		printf("This process should have increased number of threads: %d.\n", testProcessEntry->numThreads);
		
		 return i;
	} else {
		   DEBUG('a', "Process Table Full. Unable to add new ProcessTable Thread Entry.\n");
		   interrupt->Halt();
		   return -1;
	}
}

void ProcessTable::removeProcess(SpaceId id){
    if (id < 0 || id > MAXNUMPROCESSES){
        DEBUG('a', "Invalid SpaceId %d given when trying to remove a process from the ProcessTable\n", id);
			interrupt->Halt();
    }
	else {
        if(processTable->Get(id) == 0){
            DEBUG('a', "Tried to remove a non-existant process from the process Table: %d.\n", id);
		    interrupt->Halt();
        }
		else {
			delete ((KernelProcessEntry*)(processTable->Remove(id)));
            decrementNumProcesses(); // update the count of total processes
        }
     }
}

void ProcessTable::removeThread(SpaceId spaceId, ThreadId threadId){
    if (spaceId < 0 || spaceId > MAXNUMPROCESSES){
        DEBUG('a', "Invalid SpaceId %d given when trying to remove a process from the ProcessTable\n", spaceId);
		interrupt->Halt();
    }
	else {
        if(processTable->Get(spaceId) == 0){
            DEBUG('a', "Tried to remove a non-existant process %d from the process Table.\n", spaceId);
		    interrupt->Halt();
        }
		else {
			KernelProcessEntry* processEntry = ((KernelProcessEntry*)(processTable->Get(spaceId)));
			if (threadId < 0 || threadId > MAXNUMTHREADS){
				DEBUG('a', "Invalid ThreadId %d given when trying to remove a thread from process %d.\n", threadId, spaceId);
				interrupt->Halt();
			}
			else {
				if(processEntry->threadTable->Get(threadId) == 0){
					DEBUG('a', "Tried to remove a non-existant thread %d from process %d.\n", threadId, spaceId);
					interrupt->Halt();
				}
				else {
					processEntry->dataLock->Acquire();
					delete ((KernelThreadEntry*)(processEntry->threadTable->Remove(threadId)));
					--(processEntry->numThreads); // decrement the count of threads running in this process
					processEntry->dataLock->Release();
				}
			}
		}
	}
}

KernelProcessEntry* ProcessTable::getProcess(SpaceId spaceId){
    if (spaceId < 0 || spaceId > MAXNUMPROCESSES){
        DEBUG('a', "Invalid SpaceId %d given when trying to remove a process from the ProcessTable\n", spaceId);
		interrupt->Halt();
		return NULL;
    }
	else {
        if(processTable->Get(spaceId) == 0){
            DEBUG('a', "Tried to remove a non-existant process %d from the process Table.\n", spaceId);
		    interrupt->Halt();
			return NULL;
        }
        else {
			return ((KernelProcessEntry*)(processTable->Get(spaceId)));
		}
	}
}

KernelThreadEntry* ProcessTable::getThread(SpaceId spaceId, ThreadId threadId){
    if (spaceId < 0 || spaceId > MAXNUMPROCESSES){
        DEBUG('a', "Invalid SpaceId %d given when trying to remove a process from the ProcessTable\n", spaceId);
		interrupt->Halt();
		return NULL;
    }
	else {
        if(processTable->Get(spaceId) == 0){
            DEBUG('a', "Tried to remove a non-existant process %d from the process Table.\n", spaceId);
		    interrupt->Halt();
			return NULL;
        }
		else {
			KernelProcessEntry* processEntry = ((KernelProcessEntry*)(processTable->Get(spaceId)));
			if (threadId < 0 || threadId > MAXNUMTHREADS){
				DEBUG('a', "Invalid ThreadId %d given when trying to remove a thread from process %d.\n", threadId, spaceId);
				interrupt->Halt();
				return NULL;
			}
			else {
				if(processEntry->threadTable->Get(threadId) == 0){
					DEBUG('a', "Tried to remove a non-existant thread %d from process %d.\n", threadId, spaceId);
					interrupt->Halt();
					return NULL;
				}
				else {
					return ((KernelThreadEntry*)(processEntry->threadTable->Get(threadId)));
				}
			}
		}
	}
}
unsigned int ProcessTable::getNumProcesses(){
    unsigned int rv;
    numProcessesLock->Acquire();
    rv = numProcesses;
    numProcessesLock->Release();
    return rv;
}

 /*setNumProccesses
sets the number of current processes
*/
void ProcessTable::setNumProcesses(unsigned int num) {
	numProcessesLock->Acquire();
    numProcesses = num;
    numProcessesLock->Release();
}

/*incrementNumProccesses
increases the number of processes by 1.
*/
void ProcessTable::incrementNumProcesses() {
	numProcessesLock->Acquire();
    numProcesses++;
    numProcessesLock->Release();
}

/*decrementNumProccesses
decreases the number of processes by 1.
*/
void ProcessTable::decrementNumProcesses() {
	numProcessesLock->Acquire();
    numProcesses--;
    numProcessesLock->Release();
}
